package qc.module.qms.algorithm.NumericCalc.method;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import qc.common.core.utils.LocalDateTimeUtil;
import qc.module.qms.algorithm.NumericCalc.dto.*;

import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * QmsCalcMethodLinear 一元一次方程法
 *
 * @author QuCheng Tech
 * @since 2025/9/8
 */
public class QmsNumericCalcMethodLinear extends QmsNumericCalcMethodBase {
    private static final Logger logger = LoggerFactory.getLogger(QmsNumericCalcMethodLinear.class);

    /**
     * 计算方法描述：y=ax+b
     * <p>
     * 参数定义：
     * a：数值类型；系数，可以为负值或0，默认值为1
     * b：数值类型：尾数，可以为负值或0，默认值为0
     * <p>
     * 输入数据序列：1个数据序列，作为x的输入
     * <p>
     * 计算结果：以计算目标中的时间点为目标进行计算，没有对应输入数据的保持为NaN（比如是重新标记输入数据为错误值后重新进行的计算）
     */
    //与本计算方法使用配套的参数定义及默认值
    private static final String Param_A_Key = "a";
    private static final String Param_B_Key = "b";
    private static final double Param_A_DefaultValue = 1.0f;
    private static final double Param_B_DefaultValue = 0.0f;

    public QmsNumericCalcMethodLinear(QmsNumericCalcDest dest, QmsNumericCalcUseMethod method, QmsNumericCalcInput input) {
        super(dest, method, input);
    }

    @Override
    public void validInput() {
        logger.info("validInput");
        //进入调用计算方法时先获取一次计算结果中的成功标记，如果为false表示构造函数中验证未通过，输入的计算方法和目标不正确不能进行计算
        if (this.result.isSuccess() == false) {
            logger.error("validInput 初始化中验证计算目标和计算方法失败，不能进行输入数据的验证");
        } else {
            //输入参数可以为空，但输入参数不为空时判断参数的数据值类型是否正确

            //输入时序序列数据必须至少有1个序列
            List<QmsNumericCalcInputTimeSerialDatas> timeSerialDatas = this.input.getTimeSerialDatas();
            if (timeSerialDatas == null || timeSerialDatas.size() < 0x1) {
                String errorMsg = "输入时序数据序列必须至少有1个序列";
                this.result.setSuccess(false);
                this.result.setMsg(errorMsg);

                logger.error("validInput 验证输入数据未通过，" + errorMsg);
            } else {
                //获取输入时序数据中的第1个序列，判断是否实际有数据序列
                QmsNumericCalcInputTimeSerialDatas inputTimeSerialDatas = timeSerialDatas.get(0x0);
                if (inputTimeSerialDatas == null || inputTimeSerialDatas.getDatas() == null || inputTimeSerialDatas.getDatas().size() < 0x1) {
                    String errorMsg = "输入时序数据序列中第1个序列数据为空";
                    this.result.setSuccess(false);
                    this.result.setMsg(errorMsg);

                    logger.error("validInput 验证输入数据未通过，" + errorMsg);
                }
            }
        }
    }

    @Override
    public void calc() {
        logger.info("calc");
        //进入调用计算方法时先获取一次计算结果中的成功标记，如果为false表示构造函数中验证未通过，输入的计算方法和目标不正确不能进行计算
        if (this.result.isSuccess() == false) {
            logger.error("calc 初始化中验证计算目标和计算方法失败，不能进行计算");
        } else {
            //在计算方法子类中根据实际情况需要调用父类中提供的方法
            super.debugOutputMethod();
            super.debugOutputDest();
            super.debugOutputInput();

            this.validInput();

            //调试输出初始化的计算结果
            super.debugOutputResult();

            //获取计算结果中的成功标记，如果成功标记已经为false表示输入数据没有验证通过，为true时才能继续进行计算
            if (this.result.isSuccess() == false) {
                logger.error("calc 验证输入数据未通过，不能进行计算");
            } else {
                //获取本次计算需参与的参数值
                QmsNumericCalcParamValue paramValue_A = new QmsNumericCalcParamValue(this.getInputParamItem(this.destPointStcd, Param_A_Key));
                QmsNumericCalcParamValue paramValue_B = new QmsNumericCalcParamValue(this.getInputParamItem(this.destPointStcd, Param_B_Key));
                //调试输出参数值
                paramValue_A.debugOutput();
                paramValue_B.debugOutput();
                //判断如果参数只有一个在此获取参数作为默认参数值，不在遍历时间点中重复调用获取参数值方法
                boolean paramValueHasTimeDivision_A = paramValue_A.hasTimeDivision;
                boolean paramValueHasTimeDivision_B = paramValue_B.hasTimeDivision;
                double uniqueParamValue_A = paramValueHasTimeDivision_A == true ? Double.NaN : paramValue_A.getDoubleValue(null, Param_A_DefaultValue);//使用NaN表示未获取到有效参数，设置默认参数值
                double uniqueParamValue_B = paramValueHasTimeDivision_B == true ? Double.NaN : paramValue_B.getDoubleValue(null, Param_B_DefaultValue);
                logger.debug("calc 参数-计算系数a 是否有分时段参数值[" + paramValueHasTimeDivision_A + "] 唯一参数值[" + uniqueParamValue_A
                        + "]，参数-计算尾数b 是否有分时段参数值[" + paramValueHasTimeDivision_B + "] 唯一参数值[" + uniqueParamValue_B + "]");
                //初始化计算结果中的应有、实有和数据集合
                int dueResultCount = 0x0;
                int actualResultCount = 0x0;
                Map<LocalDateTime, Double> calcResultMap = new LinkedHashMap<>();
                //获取计算输入的时序数据
                Map<LocalDateTime, Double> inputDataMap = this.input.getTimeSerialDatas().get(0x0).getDatas();
                //有2中计算方法：一种是有指定计算目标时间点序列的（获取对应时间点的输入数据）、另一种是没有指定计算目标时间点的（遍历每条输入数据进行计算）
                //不管是哪种计算方法，如果获取到输入数据集合为空均不能进行计算，在此先进行判断输入数据集合是否为空
                //2025-9-27，最终确定在此不需要判断输入数据是否为空，在validInput方法中对输入数据进行验证
                if (this.result.getDatas() != null && this.result.getDatas().size() > 0x0) {
                    //设置应有数量为初始化计算结果中的应有数量
                    dueResultCount = this.result.getDueCount();
                    //有指定计算目标时间点，遍历目标时间点进行计算
                    for (Map.Entry<LocalDateTime, Double> entry : this.result.getDatas().entrySet()) {
                        LocalDateTime tm = entry.getKey();
                        Double value = entry.getValue();//初始化计算结果中的数值为null
                        logger.debug("calc 根据计算结果时间[" + LocalDateTimeUtil.formatDateTime(tm) + "]计算，初始化的计算结果值[" + value + "]");
                        //获取对应时间点的输入数据；不往前或往后取数据
                        Double inputDataValue = inputDataMap.get(tm);
                        if (inputDataValue == null) {
                            logger.warn("calc 根据计算结果时间[" + LocalDateTimeUtil.formatDateTime(tm) + "]计算，由于缺少对应时间点的输入数据无法计算");
                        } else {
                            //计算结果实有数量+1；不管计算出的数值实有有效均判定为该时间点有计算结果数据
                            actualResultCount++;
                            if (inputDataValue.isNaN() == true) {
                                logger.warn("calc 根据计算结果时间[" + LocalDateTimeUtil.formatDateTime(tm) + "]计算，由于对应时间点的输入数据值为NaN，保持计算结果值为NaN");
                            } else {
                                //输入的数据不为空并且有效才能继续进行计算
                                value = inputDataValue.doubleValue();
                                //根据是否有分时段参数标记获取本次计算使用的参数值
                                double modulus = paramValueHasTimeDivision_A == true ? paramValue_A.getDoubleValue(tm, Param_A_DefaultValue) : uniqueParamValue_A;
                                double mantissa = paramValueHasTimeDivision_B == true ? paramValue_B.getDoubleValue(tm, Param_B_DefaultValue) : uniqueParamValue_B;
                                //判断系数，如果有效则乘以系数
                                if (Double.isNaN(modulus) == false)
                                    value = value * modulus;
                                if (Double.isNaN(mantissa) == false)
                                    value = value + mantissa;

                                logger.info("calc 根据计算结果时间[" + LocalDateTimeUtil.formatDateTime(tm) + "]计算，计算结果值[" + value + "]，输入数据值["
                                        + inputDataValue.doubleValue() + "]，参数-计算系数a[" + modulus + "]，参数-计算尾数b[" + mantissa + "]");
                            }
                        }

                        calcResultMap.put(tm, value);
                    }
                } else {
                    //没有指定计算目标时间点，遍历输入数据序列计算
                    //如果是使用输入的数据序列，为保证按时间先后顺序计算在此对输入数据再次进行排序
                    Map<LocalDateTime, Double> sortedByTimeAscDatas = new LinkedHashMap<>();
                    //inputRawTimeSerialDatas.setDatas(calcInputRawDatas.entrySet().stream().sorted(Map.Entry.comparingByKey()).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
                    inputDataMap.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEachOrdered(p -> sortedByTimeAscDatas.put(p.getKey(), p.getValue()));

                    for (Map.Entry<LocalDateTime, Double> entry : sortedByTimeAscDatas.entrySet()) {
                        //根据输入数据序列计算的在此对应有数量+1、实有数量+1
                        dueResultCount++;
                        actualResultCount++;

                        LocalDateTime tm = entry.getKey();
                        double value = Double.NaN;
                        Double inputValue = entry.getValue();
                        logger.debug("calc 根据输入数据时间[" + LocalDateTimeUtil.formatDateTime(tm) + "]计算，输入数据值[" + inputValue + "]");
                        //输入数据不能为null或NaN
                        if (inputValue == null) {
                            logger.warn("calc 根据输入数据时间[" + LocalDateTimeUtil.formatDateTime(tm) + "]计算，由于缺少对应时间点的输入数据无法计算");
                        } else {
                            if (inputValue.isNaN() == true) {
                                logger.warn("calc 根据输入数据时间[" + LocalDateTimeUtil.formatDateTime(tm) + "]计算，由于对应时间点的输入数据值为NaN，保持计算结果值为NaN");
                            } else {
                                //先初始化计算结果值为输入的数值
                                value = inputValue.doubleValue();
                                //根据是否有分时段参数标记获取本次计算使用的参数值
                                double modulus = paramValueHasTimeDivision_A == true ? paramValue_A.getDoubleValue(tm, Param_A_DefaultValue) : uniqueParamValue_A;
                                double mantissa = paramValueHasTimeDivision_B == true ? paramValue_B.getDoubleValue(tm, Param_B_DefaultValue) : uniqueParamValue_B;
                                //判断系数，如果有效则乘以系数
                                if (Double.isNaN(modulus) == false)
                                    value = value * modulus;
                                if (Double.isNaN(mantissa) == false)
                                    value = value + mantissa;

                                logger.info("calc 根据输入数据时间[" + LocalDateTimeUtil.formatDateTime(tm) + "]计算，计算结果值[" + value + "]，输入数据值["
                                        + inputValue.doubleValue() + "]，参数-计算系数a[" + modulus + "]，参数-计算尾数b[" + mantissa + "]");
                            }
                        }

                        //不管是否能计算出有效的数值均将计算的时间点加入结果集合中，计算不出数值的保持数据值为NaN
                        calcResultMap.put(tm, value);
                    }

                    //设置计算结果
                    setResult(dueResultCount, actualResultCount, calcResultMap);

                    super.debugOutputResult();
                    //计算完成后调用父类方法对数值单位换算处理
                    super.calcResultValueUnitConvert();
                    //单位换算后再次输出计算结果
                    super.debugOutputResult();
                }
            }
        }
    }
}
